package com.dbflow5.query;

import com.dbflow5.config.DBFlowDatabase;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.database.SQLiteException;
import com.dbflow5.query.list.FlowCursorList;
import com.dbflow5.query.list.FlowQueryList;
import com.dbflow5.transaction.Transaction;

import java.util.List;
import java.util.function.Function;


/**
 * Description: An interface for query objects to enable you to cursor from the database in a structured way.
 * Examples of such statements are: [From], [Where], [StringQuery]
 */
public interface ModelQueriable<T> extends Queriable {

    /**
     * The table that this query comes from.
     * @return the table that this query comes from.
     */
    Class<T> table();

    /**
     * List of model converted items
     * @param databaseWrapper databaseWrapper
     * @return a list of model converted items
     */
    List<T> queryList(DatabaseWrapper databaseWrapper);

    /**
     * Single model, the first of potentially many results
     * @param databaseWrapper databaseWrapper
     * @return Single model, the first of potentially many results
     */
    T querySingle(DatabaseWrapper databaseWrapper);

    /**
     * A non-null result. throws a [SQLiteException] if the query reaches no result.
     * @param db db
     * @return A non-null result.
     */
    default T requireSingle(DatabaseWrapper db){
        T t = querySingle(db);
        if(t != null){
            return t;
        }
        throw new SQLiteException("Model result not found for " + this);
    }

    /**
     * A cursor-backed list that handles conversion, retrieval, and caching of lists.
     * @param databaseWrapper databaseWrapper
     * @return A cursor-backed list that handles conversion, retrieval, and caching of lists. Can
     * cache models dynamically by setting [FlowCursorList.setCacheModels] to true.
     */
    FlowCursorList<T> cursorList(DatabaseWrapper databaseWrapper);

    /**
     * A cursor-backed [List] that handles conversion, retrieval, caching, content changes, and more.
     * @param databaseWrapper databaseWrapper
     * @return A cursor-backed [List] that handles conversion, retrieval, caching, content changes,
     * and more.
     */
    FlowQueryList<T> flowQueryList(DatabaseWrapper databaseWrapper);

    /**
     * Returns a [List] based on the custom [TQuery] you pass in.
     *
     * @param queryModelClass The query model class to use.
     * @return A list of custom models that are not tied to a table.
     */
    //<TQuery> List<TQuery> queryCustomList(Class<TQuery> queryModelClass, DatabaseWrapper databaseWrapper);

    /**
     * Returns a single [TQueryModel] from this query.
     *
     * @param queryModelClass The class to use.
     * @return A single model from the query.
     */
    //<TQueryModel> TQueryModel queryCustomSingle(Class<TQueryModel> queryModelClass, DatabaseWrapper databaseWrapper);

    /**
     * Disables caching on this query for the object retrieved from DB (if caching enabled). If
     * caching is not enabled, this method is ignored. This also disables caching in a [FlowCursorList]
     * or [FlowQueryList] if you [.flowQueryList] or [.cursorList]
     * @return ModelQueriable
     */
    ModelQueriable<T> disableCaching();


    /**
     * Begins an async DB transaction using the specified TransactionManager.
     * @param databaseWrapper databaseWrapper
     * @param modelQueriableFn modelQueriableFn
     * @param <R> <R>
     * @return transaction builder
     */
    //modelQueriableFn: ModelQueriable<T>.(DatabaseWrapper) -> R
    default <R> Transaction.Builder<R> async(DBFlowDatabase databaseWrapper, Function<DatabaseWrapper, R> modelQueriableFn) {
        return databaseWrapper.beginTransactionAsync(modelQueriableFn);
    }

    /**
     * Attempt to constrain this [ModelQueriable] if it supports it via [Transformable] methods. Otherwise,
     * we just return itself.
     * @param offset offset
     * @param limit limit
     * @return ModelQueriable
     */
    default ModelQueriable<T> attemptConstrain(long offset, long limit) {
        if(this instanceof Transformable<?>){
            return ((Transformable<T>)this).constrain(offset, limit);
        }
        return this;
    }

    /**
     * Trims and wraps a [ModelQueriable.query] in parenthesis.
     * E.G. wraps: select * from table into (select * from table)
     * @return parenthesis string
     */
    default String enclosedQuery() {
        return new StringBuilder().append("(").append(getQuery().trim()).append(")").toString();
    }

    default <V> List<V> queryCustomList(Class<V> clazz, DatabaseWrapper db) {
        return queryCustomList(clazz, db);
    }

    default <V> V queryCustomSingle(Class<V> clazz, DatabaseWrapper db) {
        return queryCustomSingle(clazz, db);
    }

    default <V> V requireCustomSingle(Class<V> clazz, DatabaseWrapper db) {
        V v = queryCustomSingle(clazz, db);
        if(v == null){
            throw new SQLiteException("QueryModel result not found for "+ this);
        }
        return v;
    }

    /**
     * Extracts the [From] from a [ModelQueriable] if possible to get [From.associatedTables]
     * @return From<T>
     */
    default From<T> extractFrom() {
        if (this instanceof From<?>) {
            return (From<T>)this;
        } else if (this instanceof Where<?> && ((Where<?>)this).whereBase instanceof From<?>) {
            return ((From<T>)(((Where<?>)this)).whereBase);
        } else {
            return null;
        }
    }
}
